#include "bzbinput/input.h"


#ifdef WINDOWS

#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0500
#include "windows.h"
#include "winuser.h"
#include "wincon.h"

#elif defined(LINUX)

using u16 = unsigned short;
// On some systems limits.h doesn't contain u16
// #include <linux/limits.h>
#include <sys/time.h>
#include <linux/uinput.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <dirent.h>
#include <cstring>
#include <thread>
#include <chrono>

FILE *physkb = nullptr;

#define INPUT_KEY_MAX 0x2ff

#endif

using bzbinput::Key;


#ifdef LINUX


int bzbinput::new_virtual_kb_device(const char* name,
									const unsigned int wait_time,
									const unsigned short bus_type,
									const unsigned short vendor,
									const unsigned short product,
									const unsigned short version) {

	int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);

	if( fd < 0 ||
		ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0) {

		bzbutil::throw_sys_error();
	}

	for(u16 i = 0; i < INPUT_KEY_MAX; i++) {
		if(ioctl(fd, UI_SET_KEYBIT, i) < 0) {
			bzbutil::throw_sys_error();
		}
	}

	struct uinput_user_dev keyboard;
	memset(&keyboard, 0, sizeof(struct uinput_user_dev));
	keyboard.id.bustype = bus_type;
	keyboard.id.vendor = vendor;
	keyboard.id.product = product;
	keyboard.id.version = version;
	snprintf(keyboard.name, UINPUT_MAX_NAME_SIZE, "%s", name);

	if(write(fd, &keyboard, sizeof(struct uinput_user_dev)) < 0) {
		bzbutil::throw_sys_error();
	}

	if(ioctl(fd, UI_DEV_CREATE) < 0) {
		bzbutil::throw_sys_error();
	}

	if(wait_time)
		std::this_thread::sleep_for(std::chrono::milliseconds(wait_time));

	return fd;
}


bool bzbinput::destroy_virtual_kb_device(const int devfd) {

	if(ioctl(devfd, UI_DEV_DESTROY) < 0){
		return false;
	}

	return close(devfd) >= 0;
}

#endif


#ifdef WINDOWS

Key bzbinput::get_key(const unsigned delta) {

	HWND window = GetConsoleWindow();

	// Wait until a button is pressed
	while(true) {

		// Check if the console window is focused
		// Remove this to catch system-wide key presses
		if(GetForegroundWindow() == window) {

			for (int i = 1; i < 255; ++i) {

				if(GetAsyncKeyState(i) & 0x07)
					return static_cast<Key>(i);
			}

		}

		Sleep(delta);
	}
}

#elif defined(LINUX)


bool bzbinput::is_input_available() {

	fd_set fds;
	FD_ZERO(&fds);
	FD_SET(STDIN_FILENO, &fds);

	// Don't block to wait input
	timeval tm;
	tm.tv_sec = 0;
	tm.tv_usec = 0;

	select(STDIN_FILENO + 1, &fds, nullptr, nullptr, &tm);

	return FD_ISSET(0, &fds);
}


#include "bzbio.h"
using namespace bzbio;

Key bzbinput::get_key(const unsigned delta) {

	termios old_term;
	termios new_term;

	int res = tcgetattr(STDIN_FILENO, &old_term);
	if(res) {
		bzbutil::throw_sys_error();
	}

	cfmakeraw(&new_term);
	new_term.c_lflag = 0;
	new_term.c_cc[VTIME] = 0;
	new_term.c_cc[VMIN] = 1;
	//new_term.c_cc |= (unsigned char) VEOF;
	tcsetattr(STDIN_FILENO, TCSANOW, &new_term);

	if(delta) {
		while(!bzbinput::is_input_available()) {
			usleep(16000);
		}
	} else if(!bzbinput::is_input_available()) {
		return (Key) 0;
	}
	
	char key = 0;
	read(STDIN_FILENO, (void*) &key, 1);

	// Additional code to read 

	// char key2 = 0;
	// char key3 = 0;

	// if(key == Key::ESC && is_input_available()) {

	// 	read(STDIN_FILENO, (void*) &key2, 1);
	// 	read(STDIN_FILENO, (void*) &key3, 1);

	// 	if(key2 == '[') {
	// 		switch(key3) {
	// 			case 'A': key = Key::UP; break;
	// 			case 'B': key = Key::DOWN; break;
	// 			case 'C': key = Key::RIGHT; break;
	// 			case 'D': key = Key::LEFT; break;
	// 			default: key = Key::END; break;
	// 		}
	// 	}
	// }

	res = tcsetattr(STDIN_FILENO, TCSANOW, &old_term);
	if(res) {
		bzbutil::throw_sys_error();
	}

	return (Key) key;
}

#endif

#ifdef WINDOWS

void bzbinput::fake_put(const Key key) {

	INPUT input;
	input.type = 1;
	input.ki.wVk = (int) key;
	input.ki.wScan = 0;
	input.ki.dwFlags = 0;
	input.ki.time = 0;
	input.ki.dwExtraInfo = GetMessageExtraInfo();

	int res = SendInput(1, &input, sizeof(INPUT));
	if(!res) {
		bzbutil::throw_sys_error(res);
	}
}

#elif defined(LINUX)

bool bzbinput::fake_put(const int devfd, const Key key,
						const KeyPressType type) {

	 static struct input_event event;

	 gettimeofday(&event.time, nullptr);
	 event.type = EV_KEY;
	 event.code = static_cast<u16>(key);
	 event.value = static_cast<u16>(type);

	 if(write(devfd, &event, sizeof(struct input_event)) < 0) {
		  return false;
	 }

	 event.type = EV_SYN;
	 event.code = SYN_REPORT;
	 event.value = 0;

	 return write(devfd, &event, sizeof(struct input_event)) >= 0;
}

#endif
